package network.managers;

import java.io.IOException;
import java.util.HashSet;

import state.Globals;
import state.group.events.BaseEvent;
import state.group.events.EventCollection;

import messages.BaseMessage;
import messages.MessageUtils;
import messages.observer.EventCollectionMessage;
import network.server.SocketThread;

/**
 * This class is responsible for keeping track of the
 * Group Dispatcher's Observers, as well as relaying 
 * information to them periodically.
 * 
 * @author Ian Doerr
 */
public class ObserverManager implements Runnable {
    
    private static ObserverManager manager;
    private static Thread runThread;
    
    /**
     * Return the current instance of the Observer Manager
     * 
     * @return
     */
    public static ObserverManager getInstance()
    {
        return manager;
    }
    
    /**
     * Initialize the Observer Manager
     * 
     * @param strategy
     */
    public static void init()
    {
        manager = new ObserverManager();
        runThread = new Thread(manager, "Observer Manager Thread");
        runThread.start();
    }
    
    /**
     * Notify the manager thread that it needs to shut down.
     * Return after the thread has successfully shut down.
     */
    public static void shutdown()
    {
        manager.shouldContinue = false;
        synchronized(manager)
        {
            manager.notifyAll();
        }
        
        try {
			runThread.join();
		} catch (InterruptedException e) {
			//NOOP
		}
    }
    
    private boolean shouldContinue = true;
    private HashSet<SocketThread> observers = new HashSet<SocketThread>();
    private EventCollection collection = new EventCollection();
    
    /**
     * Disallow instantiation outside of this class.
     */
    private ObserverManager()
    {
        
    }
    
    /**
     * Register the given ModeHandlerThread as an observer
     * 
     * @param handler
     * @return success
     */
    public boolean registerObserver(SocketThread handler)
    {
        synchronized (observers)
        {
            return observers.add(handler);
        }
    }
    
    /**
     * Deregister the given ModeHandlerThread as an observer
     * 
     * @param handler
     * @return success
     */
    public boolean deregisterObserver(SocketThread handler)
    {
        synchronized (observers)
        {
            return observers.remove(handler);
        }
    }
    
    /**
     * Register an event to be sent out later.
     * 
     * @param event
     */
    public synchronized void postEvent(BaseEvent event)
    {
        if (Globals.isDebug())
        {
            Globals.getLogger().log("Observer Manager recording event: " + event.toString());
        }
        collection.add(event);
    }
    
    /**
     * This is the run method of the Observer Manager
     */
    @Override
    public void run()
    {
        while (shouldContinue)
        {
            //wait until notified that it should send out events
            EventCollectionMessage messageToSend = null;
            synchronized (this)
            {
                try
                {
                    wait();
                }
                catch (InterruptedException e)
                {
                    // NOOP
                }
                
                if (collection.isEmpty())
                {
                    messageToSend = null;
                }
                else
                {
                    messageToSend = new EventCollectionMessage(collection);
                    collection.clear();
                }
            }
            
            if (!shouldContinue)
            {
                break;
            }
            
            //send out the Event Collection to the observers
            if (messageToSend != null)
            {            
                sendMessageToObservers(messageToSend);
            }
        }
    }
    
    /**
     * Send the given message to all of the registered observers.
     * 
     * @param message
     */
    public void sendMessageToObservers(BaseMessage message)
    {
        byte[] buffer = message.toByteArray();
        
        if (Globals.isDebug())
        {
        	Globals.getLogger().log("Observer Manager sending out message: " + MessageUtils.byteArrayToString(buffer));
        }
        
        SocketThread[] threads;
        
        //get a shallow copy of the observers
        synchronized (observers)
        {
            threads = observers.toArray(new SocketThread[0]);
        }
        
        //send out the message to all the observers.
        //TODO use a fork here?
        for (SocketThread thread : threads)
        {
            try
            {
                thread.getRemoteSocket().getOutputStream().write(buffer);
            }
            catch (IOException e)
            {
                Globals.getLogger().error("Error occurred while sending a message to an observer!", e);
            }
        }
    }
    
}
